package an_sim;

import java.util.*;
import java.util.ArrayList;

/**
 * Ez az osztály reprezentálja a virtuális részhálózatokat.
 * Tartalmazza a virtuális hálózatban található Node-ok listáját,
 * a virtuális hálózatot reprezentáló SuperNode-ot,
 * a hierarichiaszintjét, a felette és alatt található hálózatok ID-ját,
 * és a folyamatai által felhasznált időt.
 * @author D-Torus-Team
 */
public class VirtualNetwork {

    private List<Node> members;
    private Node superNode;
    private int hierarchyLevel;
    private List<Integer> gatewayIdDown;
    private int gatewayIdUp;
    private boolean busy;
    private Time time;

    /**
     * Konstruktor.
     * @param m taglista
     * @param p policy
     * @param h hierarchia szint
     * @param giddown alatt található vn-ek ID listája
     * @param gidup felette található vn ID-ja
     */
    VirtualNetwork(List<Node> m, Policy p, int h, List<Integer> giddown, int gidup) {
        time = new Time();
        members = new ArrayList<Node>();
        members.addAll(m);

        // A tagok szomszédainak átadása a SuperNode-nak
        List<Integer> neighbours = new ArrayList<Integer>();
        Iterator<Node> it = m.iterator();

        while (it.hasNext()) {
            neighbours.addAll(it.next().getNeighbours());
        }

        // Eltávolítja a duplikált elemeket
        Set set = new HashSet();
        List newList = new ArrayList();
        for (Iterator iter = neighbours.iterator(); iter.hasNext();) {
            Object element = iter.next();
            if (set.add(element)) {
                newList.add(element);
            }
        }
        neighbours.clear();
        neighbours.addAll(newList);

        // Egyéb beállítások
        superNode = new Node(p, newNeighboursList(neighbours, -1, -1));
        busy = false;
        gatewayIdDown = giddown;
        gatewayIdUp = gidup;
        hierarchyLevel = h;


    }

    public Time getTime() {
        return time;
    }

    public int getGatewayIdUp() {
        return gatewayIdUp;
    }

    public List<Integer> getGatewayIdDown() {
        return gatewayIdDown;
    }

    public void setGatewayIdUp(int gatewayIdUp) {
        this.gatewayIdUp = gatewayIdUp;
    }

    public boolean isBusy() {
        return busy;
    }

    public int getHierarchyLevel() {
        return hierarchyLevel;
    }

    public List<Node> getMembers() {
        return members;
    }

    /**
     * Ellenörzi, hogy a becsatlakozni kívánó vn nem egy alatta lévő vn-hez próbál-e becsatlakozni.
     * @param in A becsatlakozni akaró vn.
     * @param fn full network
     * @param gatewayList  Ebbe a listűba fogjuk tárolni ezen vn felett lévő gw-jek ID-ját
     * @return igaz, ha a saját gw-je, egyébként hamis
     */
    public boolean checkGW(VirtualNetwork vn, VirtualNetwork in, FullNetwork fn, List<Integer> gatewayList) {
        
        //System.out.println("TestID: " + vn.superNode.getID());
      

        //Ha van valaki felette, akkor annak az id-ját belerakjuk a listánkba, és meghívjuk a függvényt a felette lévőre.
        if (vn.gatewayIdUp != 0) {
            gatewayList.add(vn.superNode.getID());
            checkGW(fn.getVNByID(vn.gatewayIdUp), in, fn, gatewayList);
        }

        //Ha már nincs felette senki, akkor a becsatlakozni kívánó vn gatewayIdDown listáján végigiterálva ellenőrizzük, hogy semelyik sincs-e benne a most létrehozott listánkban
        // Ha valamelyik benne van, akkor true eredménnyel visszatérünk.
        if (gatewayList.size() > 0) {
            Iterator<Integer> it = in.gatewayIdDown.iterator();
            while (it.hasNext()) {
                if (gatewayList.contains(it.next())) {
                    return true;
                }
            }
        }
        //Ha egyik sincs benne akkor false eredménnyel térünk vissza.
        return false;
    }

    /**
     * Ez a függvény végzi el az új nodeok szomszédlistájának a kialakításást.
     * @param nN szomszédlista amit hozzá kell adni az új listához
     * @param oldIDthis csatlakozásnál az egyik fél ID-ja
     * @param oldIDin csatlakozásnál a másik fél ID-ja
     * @return az új szomszédlista
     */
    private List<Integer> newNeighboursList(List<Integer> nN, int oldIDthis, int oldIDin) {

        // Létrehozunk egy új listát amibe beletesszük a SuperNode szomszédait.
        // A konstruktorban is meghívjuk a SuperNode léterhozásakor, így abban az esetben a SuperNode még nem létezik.
        List<Integer> neighbours = new ArrayList<Integer>();
        if (this.superNode != null) {
            neighbours.addAll(this.superNode.getNeighbours());
        }

        // A paraméterként kapott szomszédlistán végigmegyünk, és ha az új szomszédlistánk már tartalmazza akkor nem adjuk hozzá.
        Iterator<Integer> it = nN.iterator();
        while (it.hasNext()) {
            int temp = it.next();
            if (!neighbours.contains(temp)) {
                neighbours.add(temp);
            }
        }

        // Az abszorbció során a két egyesülő fél ID-ját kivesszük a szomszédlistából.
        if (neighbours.contains(oldIDthis)) {
            neighbours.remove(neighbours.indexOf(oldIDthis));
        }
        if (neighbours.contains(oldIDin)) {
            neighbours.remove(neighbours.indexOf(oldIDin));
        }

        // Visszadja az új szomszédlistát
        return neighbours;
    }

    /**
     * Ez a fv. felelős az egyesülések során a szomszédok szomszédlistáinak frissítéséért.
     * @param oldid1 ennek a vn-nek a régi id-ja
     * @param oldid2 a becsatlakozó vn régi id-ja
     * @param fn full network
     */
    private void UpdateNeighbours(int oldid1, int oldid2, FullNetwork fn) {

        // Ennek a vn-nek végigmegyünk a szomszédain és kiszedjük a két abszorbáló fél ID-ját a szomszédok szomszédlistájából.
        Iterator<Integer> it = this.superNode.getNeighbours().iterator();
        while (it.hasNext()) {
            int tmp = it.next();
            // Ha a szomszéd még létezik
            if (fn.getVNByID(tmp) != null) {
                // Frissítjük a szomszédlistáját
                List<Integer> temp = fn.getVNByID(tmp).getSuperNode().getNeighbours();
                if (temp.contains(oldid1)) {
                    temp.remove(temp.indexOf(oldid1));
                }
                if (temp.contains(oldid2)) {
                    temp.remove(temp.indexOf(oldid2));
                }
                // Hozzáadjuk az új SuperNode ID-ját a szomszédlistájához
                temp.add(superNode.getID());

                //Eltávolítja a duplikált elemeket a szomszédok szomszédlistájából
                Set set = new HashSet();
                List newList = new ArrayList();
                for (Iterator iter = temp.iterator(); iter.hasNext();) {
                    Object element = iter.next();
                    if (set.add(element)) {
                        newList.add(element);
                    }
                }
                temp.clear();
                temp.addAll(newList);

                // Ha a szomszéd már nem létezik, kivesszük ezen vn szomszédlistájából
            } else {
                if (this.superNode.getNeighbours().contains(tmp)) {
                    this.superNode.getNeighbours().remove(superNode.getNeighbours().indexOf(tmp));
                    it = superNode.getNeighbours().iterator();
                }
            }
        }
    }

    /**
     * Frissíti a vn lefelé mutató ID listáját és az alatta található hálózatok felfelé mutató ID-ját.
     * @param in
     * @param fn
     */
    private void updateGwDown(VirtualNetwork in, FullNetwork fn) {

        // Hozzáadjuk a becsatlakozott elem lefelé mutató ID listáját ezen vn listájához
        this.gatewayIdDown.addAll(in.gatewayIdDown);

        // Végigmegyünk a lefelé mutató listán, és átállítjuk a vn-ek felfelé mutató ID-ját az új vn ID-jára
        Iterator<Integer> gwD = this.gatewayIdDown.iterator();
        while (gwD.hasNext()) {
            VirtualNetwork temp = fn.getVNByID(gwD.next());
            temp.gatewayIdUp = this.getSuperNode().getID();
        }

        //Eltávolítja a duplikált elemeket ezen vn lefelé mutató listájából
        Set set = new HashSet();
        List newList = new ArrayList();
        for (Iterator iter = this.gatewayIdDown.iterator(); iter.hasNext();) {
            Object element = iter.next();
            if (set.add(element)) {
                newList.add(element);
            }
        }
        this.gatewayIdDown.clear();
        this.gatewayIdDown.addAll(newList);
    }

    /**
     * Ez a függvény felelős az abszorbció végrehajtásáért, és a szomszédlisták, gatewayID-k frissítéséért.
     * A becsatlakozni kívánó vn meg fog szünni, a tulajdonságait az a vn fogja valamilyen formában viselni, amelyikhez becsatlakozik, de mivel új supernode lesz,
     * így az újonnan kialakult vn-nek új id-je is lesz.
     * @param in Ez a VN volt a random kiválasztott. Ő akar BEabszorbálni valamelyik szomszédjához.
     * @param fn Ez a teljes fullNetwork, azért kell átadni, hogy elérjük a függvényeit.
     */
    public void absorbtion(VirtualNetwork in, FullNetwork fn) {

        //Ellenőrizzük, hogy nem egy saját gateway-hez akar-e csatlakozni.
        List<Integer> gatewayList = new ArrayList();


        //Az egyszerűség kedvéért, ha valaki van már a csatlakozni kívánó felett, annak nem engedünk abszorbciót, de másik irányból igen
        if (in.gatewayIdUp > 0) {
            System.out.println(" \t Valaki van " + in.superNode.getID() + " felett.");
            return;
        } else if (checkGW(this, in, fn, gatewayList)) { //Ellenőrizzük, hogy nem egy saját gateway-hez akar-e csatlakozni.
            System.out.println("Nana, saját gateway-jel nem közösülünk...");
            return;
        }

        //Kiírja, hogy abs. lesz, és ki kivel.
        System.out.print("Abs lesz: " + this.superNode.getID() + " + " + in.getSuperNode().getID());

        //A becsatlakozó vn tagjait hozzáadjuk ehhez a vn-hez.
        this.members.addAll(in.members);

        //Lementjük a két vn eredeti id-jét, hogy a szomszédlisták frissítésekor fel tudjuk majd használni
        int oldIDin = in.superNode.getID();
        int oldIDthis = this.getSuperNode().getID();


        //Létrehozunk egy új supernode-ot. Lényegében ez fogja reprezentálni az abszorbciót, mert ez már az új policyvel, és szomszédlistával fog létrejönni.
        Node newSuperNode = new Node(superNode.getPolicy().newPolicyAbs(in.superNode.getPolicy()), newNeighboursList(in.superNode.getNeighbours(), oldIDin, oldIDthis));

        //Beállítja az időt, a nagyobb időt viszi tovább
        if (this.time.getSumTime() >= in.time.getSumTime()) {
            this.time.addTime(this.time.absorbtionTime);
        } else {
            in.time.addTime(in.time.absorbtionTime);
            this.time = in.time;
        }
//        if (in.gatewayIdUp != 0) {
//            gatewayIdUp = in.gatewayIdUp;
//        }


        //Átállítjuk a supernode-ot az új supernode-ra.
        this.superNode = newSuperNode;

        //Frissíteni kell azoknak a vn-eknek a gatewayUP izéjüket, akik ezen vn alatt vannak (azaz ez a gateway-ük)
        updateGwDown(in, fn);

        //A szomszédok szomszédlistáit frissíti
        UpdateNeighbours(oldIDin, oldIDthis, fn);

        //Ha az in hierarchia szintje nagyobb volt, akkor az lesz az új hierarchia szint
        if(in.hierarchyLevel>this.hierarchyLevel){
            this.hierarchyLevel=in.hierarchyLevel;
        }

        //Ha volt ezen vn felett gateway, akkor annak át kell állítani az egyik gwDown-ját.
        //Ehhez ellenőrizzük a gw downId-jai közül, hogy egyezik-e valamelyik a régi ID-val, és azt cseréljük

        if (this.gatewayIdUp != 0) {
            List<Integer> downTemp = fn.getVNByID(this.gatewayIdUp).gatewayIdDown;
            if (downTemp.contains(oldIDthis)) {
                downTemp.remove(downTemp.indexOf(oldIDthis));
            }
            downTemp.add(this.superNode.getID());


        }

        //Mindezek után a fullNetworkből el kell távolítani a becsatlakozott vn-t
        fn.getVirtualNetworkList().remove(in); //ugyan ez benne van a simulate-ben is

        System.out.println(" Eltávolítva: " + oldIDin + " Új ID: " + this.superNode.getID());
    }

    /**
     * Ez a függvény felelős a gatewaying végrehajtásáért, és a szomszédlisták, gatewayID-k frissítéséért.
     * A két vn SuperNode-ja alkot egy új vn-t (gateway) egyel magasabb hierarchiaszinten.
     * @param in becsatlakozni kívánó vn (ezt választjuk random)
     * @param fn full network
     * @return új vn (gateway)
     */
    public VirtualNetwork gatewaying(VirtualNetwork in, FullNetwork fn) {
       
        List<Integer> gatewayList = new ArrayList<Integer>();
        if (in.gatewayIdUp > 0) {
            System.out.println(" \t Valaki van " + in.superNode.getID() + " felett.");
            return new VirtualNetwork(members, null, -10, null, 0);
        } else if (checkGW(this, in, fn, gatewayList)) { //Ellenőrizzük, hogy nem egy saját gateway-hez akar-e csatlakozni.
            System.out.println("Nana, saját gyerekkel nem közösülünk...");
            return null;
        }

         System.out.println(" Gw lesz: " + this.superNode.getID() + " + " + in.getSuperNode().getID());
         
        // Ha egyik fél felett sincs gateway
        if (this.gatewayIdUp == 0 && in.gatewayIdUp == 0) {
            System.out.print(" Egyik felett sincs gw. ");

            // A két SuperNode lesz a gateway tagja
            List<Node> sn = new ArrayList();
            sn.add(superNode);
            sn.add(in.superNode);

            // A hierarchiaszintet beállítjuk a magasabbik szintje+1-re
            int h;
            if (this.hierarchyLevel < in.hierarchyLevel) {
                h = in.hierarchyLevel + 1;
            } else {
                h = this.hierarchyLevel + 1;
            }

            // Az újjonan létrejövő vn-nek a lefelé mutató listájába belerakjuk azt a két vn-t, akik miatt létrejön
            //Ehhez létrehozunk egy ideiglenes listát, és az új vn létrehozásakor átadjuk a konstruktorának
            List<Integer> gwIdDown = new ArrayList<Integer>();
            gwIdDown.add(this.getSuperNode().getID());
            gwIdDown.add(in.getSuperNode().getID());

            // Létrehozzuk az új vn-t, tagjai a két SuperNode, Policy a megfelelő függvény által átadott policy, hierarchiaszintje
            // az új hierarchiaszint, lefelé mutató listája a fentebb kialakított lista, felfelé nem mutat semmi
            VirtualNetwork vntemp = new VirtualNetwork(sn, superNode.getPolicy().newPolicyGw(in.superNode.getPolicy()), h, gwIdDown, 0);

            /* Ki kell szedni a szomszédlistából a supernode-ok által képviselt tagokat.*/
            Iterator<Integer> it = vntemp.superNode.getNeighbours().iterator();
            while (it.hasNext()) {
                int temp = it.next();
                if (temp == this.superNode.getID()) {
                    vntemp.superNode.getNeighbours().remove(vntemp.superNode.getNeighbours().indexOf(temp));
                    it = vntemp.superNode.getNeighbours().iterator();
                }
                if (temp == in.superNode.getID()) {
                    vntemp.superNode.getNeighbours().remove(vntemp.superNode.getNeighbours().indexOf(temp));
                    it = vntemp.superNode.getNeighbours().iterator();
                }

            }

            // A két fél felfelé mutató ID-ját beállítjuk az új vn ID-jára
            this.setGatewayIdUp(vntemp.getSuperNode().getID());
            in.setGatewayIdUp(vntemp.getSuperNode().getID());

            System.out.println("Új Gw id-ja: " + this.getGatewayIdUp());

            //Végigmegy a gyerekeinek a szomszédain, és beírja magát a szomszédsági listájukba:
            Iterator<Integer> gwUpdate = vntemp.superNode.getNeighbours().iterator();
            while (gwUpdate.hasNext()) {
                fn.getVNByID(gwUpdate.next()).superNode.addNeighbours(vntemp.superNode.getID());
            }

            //Beállítja az időt, a nagyobb időt viszi tovább
            if (this.time.getSumTime() >= in.time.getSumTime()) {
                vntemp.time.addTime(this.time.gatewayingTime);
                vntemp.time.addTime(this.time.getSumTime());
            } else {
                vntemp.time.setSumTime(in.time.getSumTime() + time.gatewayingTime);
            }

            return vntemp;

            // Ha van afelett a vn felett gateway akihez csatlakozni akar az kezdeményező
        } else if (this.gatewayIdUp > 0 && in.gatewayIdUp == 0) {
            System.out.print(" " + this.superNode.getID() + " felett van Gw. Felküldés: " + this.gatewayIdUp + " ");

            // Kell a simulatenek paraméterként, de nincs hatása
            Random r = new Random();

            // Felküldés idejét hozzáadjuk a csatlakozni kívánó fél idejéhez
            in.time.addTime(time.upTime);

            // Meghívjuk a simulate függvényt irányítottan a két félre
            fn.simulate(false, in, fn.getVNByID(this.gatewayIdUp), r);

            // Simulate számára egy kamu vn, hogy le tudjuk kezelni, azt, hogy a valós gateway vn az előző sorban lévő simulate függvény fogja visszaadni
            // és ne próbálkozzon tovább a szomszédokon (lásd Simulate)
            return new VirtualNetwork(members, null, -10, null, 0);

            // Ha mindkető felett van gateway akkor inkább mással próbálkozik. mert kilépés még nincs
        } else if (this.gatewayIdUp > 0 && in.gatewayIdUp > 0) {
            System.out.println("Valószínűleg mindkettő fölött van Gw. ");
        }

        // Ha minden ágon végigment és elér idáig akkor nem jön létre gateway
        System.out.println("Nem jött létre gateway: (" + this.superNode.getID() + "," + in.superNode.getID() + ") null-al fog visszatérni");
        return null;


    }

    public Node getSuperNode() {
        return superNode;
    }

    public List<Integer> getNeighbours() {
        return this.superNode.getNeighbours();
    }
}
